fix(roadmap): harden generation startup and artifact parsing#1958
fix(roadmap): harden generation startup and artifact parsing#1958VDT-91 wants to merge 3 commits intoAndyMik90:developfrom
Conversation
📝 WalkthroughWalkthroughIntroduces abort/cancellation support throughout authentication and client creation flows, adds per-request and total timeout mechanisms for OAuth token refresh, implements JSON repair with illegal control character stripping, and adds phase mapping for roadmap generation events. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the reliability and responsiveness of the roadmap generation process. It addresses critical issues where roadmap startup could exceed user-defined timeouts due to uncancelable background authentication tasks, and where discovery failed because of malformed JSON artifacts containing illegal control characters. By integrating abort signals throughout the authentication and client creation pipeline, and by introducing a resilient JSON repair mechanism, the changes ensure that roadmap generation respects user cancellations and successfully processes multilingual data. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces significant hardening for the roadmap generation process by propagating abort signals for timeouts and user cancellations, and by adding a robust JSON repair mechanism for artifacts. The changes are well-implemented and include relevant tests. My review includes a few suggestions to further improve error handling and maintainability by enhancing diagnostics, ensuring consistent error propagation, and reducing code duplication.
| } catch { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
This broad catch block silently swallows all errors that might occur within the try block, including file system errors (e.g., from readFileSync, writeFileSync, renameSync) and JSON parsing/repair errors. While returning null for invalid JSON is intended, masking potentially critical file system errors (like permission issues) can make debugging very difficult. I recommend logging the error before returning null to improve diagnostics.
} catch (error) {
console.error(`[readAndRepairJsonFile] Failed to read, repair, or parse ${filePath}:`, error);
return null;
}| function throwIfAborted(signal?: AbortSignal): void { | ||
| if (!signal?.aborted) return; | ||
|
|
||
| const reason = signal.reason; | ||
| if (reason instanceof Error) { | ||
| throw reason; | ||
| } | ||
|
|
||
| throw new Error(typeof reason === 'string' ? reason : 'Aborted'); | ||
| } |
There was a problem hiding this comment.
This throwIfAborted helper function is also defined in apps/desktop/src/main/ai/auth/resolver.ts and apps/desktop/src/main/claude-profile/token-refresh.ts. To improve maintainability and avoid code duplication, consider extracting it into a shared utility file, for example in apps/desktop/src/main/utils/.
| throw new Error('Aborted'); | ||
| } | ||
| if (clientCreationTimeoutController.signal.aborted) { | ||
| throw new Error('Client creation timed out — check your authentication credentials'); | ||
| } | ||
| throw error; | ||
| } finally { |
There was a problem hiding this comment.
The error handling for aborts could be improved. When the external abortSignal is aborted, this catch block throws a new generic Error('Aborted'), which loses the original AbortSignal.reason that might contain more specific context. Other parts of this PR preserve the reason (e.g., in token-refresh.ts). For consistency and better diagnostics, I suggest propagating the original reason. I've also reordered the checks to prioritize the more specific timeout error.
if (clientCreationTimeoutController.signal.aborted) {
throw new Error('Client creation timed out — check your authentication credentials');
}
if (abortSignal?.aborted) {
throw abortSignal.reason ?? new Error('Aborted');
}
throw error;| if (abortSignal?.aborted) { | ||
| throw new Error('Aborted'); | ||
| } |
There was a problem hiding this comment.
Bug: The catch block in runRoadmapGeneration discards the original abortSignal.reason on user cancellation, throwing a generic 'Aborted' error instead of preserving the specific reason.
Severity: MEDIUM
Suggested Fix
Modify the catch block to preserve the original abort reason. Instead of throw new Error('Aborted'), use a pattern like throw (abortSignal.reason instanceof Error ? abortSignal.reason : new Error('Aborted')) to ensure the specific reason is propagated.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: apps/desktop/src/main/ai/runners/roadmap.ts#L524-L526
Potential issue: In `runRoadmapGeneration`, when an operation is aborted via the
user-provided `abortSignal`, the `catch` block at line 524 discards the original abort
reason. It checks `if (abortSignal?.aborted)` and throws a generic `new
Error('Aborted')` instead of preserving the potentially more informative
`abortSignal.reason`. This is inconsistent with the abort-handling pattern used in other
parts of the codebase, such as `token-refresh.ts`. This results in the loss of
diagnostic context, as the user is shown a generic error message, making debugging more
difficult.
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/src/main/ai/auth/types.ts`:
- Around line 62-63: Add an optional AbortSignal parameter to
resolveAuthFromProviderQueue by changing its signature to accept abortSignal?:
AbortSignal, then forward that abortSignal into both resolveAuthFromQueue(...)
and the fallback resolveAuth(...) calls inside resolveAuthFromProviderQueue so
cancellation propagates to the queue path and the fallback path; mirror the
pattern used in factory.ts where resolveAuth calls receive the abortSignal.
Ensure parameter name matches abortSignal and thread it through any intermediate
calls within resolveAuthFromProviderQueue.
In `@apps/desktop/src/main/ai/runners/roadmap.ts`:
- Around line 41-50: The phase loops still catch aborts and treat them as normal
phase failures; update runDiscoveryPhase() and runFeaturesPhase() to use the
same abort-guard pattern as throwIfAborted/createSimpleClient: when catching
errors inside their loops, immediately rethrow if the provided AbortSignal is
aborted (or the caught error is an AbortError) instead of pushing it into errors
— i.e., call throwIfAborted(signal) at the start of each catch block (or check
signal?.aborted/instanceof Error reason and rethrow) so cancellation stops
execution immediately rather than being recorded as a retriable phase error.
- Around line 109-123: The function readAndRepairJsonFile currently swallows all
errors (I/O and parse) and returns null, which masks filesystem failures; change
it so only JSON parse/repair errors map to null while read/write/rename failures
are allowed to throw. Concretely: perform
readFileSync/repair/writeFileSync/renameSync and JSON.parse outside a broad
catch or rework the try/catch so that only JSON.parse (or a caught SyntaxError
from parsing the repaired string) is converted to a null return; do not catch or
suppress errors from readFileSync, writeFileSync, or renameSync. Reference:
readAndRepairJsonFile and callers such as loadPreservedFeatures to ensure they
still treat null as "invalid JSON" only.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c9c39732-e69f-4585-b52b-5d241087abf8
📒 Files selected for processing (13)
apps/desktop/src/main/__tests__/agent-queue.test.tsapps/desktop/src/main/agent/agent-queue.tsapps/desktop/src/main/ai/auth/resolver.tsapps/desktop/src/main/ai/auth/types.tsapps/desktop/src/main/ai/client/factory.tsapps/desktop/src/main/ai/client/types.tsapps/desktop/src/main/ai/providers/oauth-fetch.tsapps/desktop/src/main/ai/runners/roadmap.test.tsapps/desktop/src/main/ai/runners/roadmap.tsapps/desktop/src/main/claude-profile/token-refresh.test.tsapps/desktop/src/main/claude-profile/token-refresh.tsapps/desktop/src/main/utils/__tests__/json-repair.test.tsapps/desktop/src/main/utils/json-repair.ts
| /** Abort signal for cancellation during credential resolution */ | ||
| abortSignal?: AbortSignal; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify resolveAuth call sites and whether abortSignal is forwarded.
# Expected: call paths that can be canceled should thread abortSignal into resolveAuth context.
rg -nP --type=ts '\bresolveAuth\s*\(' apps/desktop/src/main -C 3
rg -nP --type=ts '\bresolveAuthFromQueue\s*\(' apps/desktop/src/main -C 3
rg -nP --type=ts 'abortSignal\s*:' apps/desktop/src/main/ai/auth -C 2Repository: AndyMik90/Aperant
Length of output: 24470
🏁 Script executed:
# Check the AuthResolverContext type definition to confirm abortSignal is optional
head -100 apps/desktop/src/main/ai/auth/types.ts | tail -50Repository: AndyMik90/Aperant
Length of output: 1792
🏁 Script executed:
# Get more context around agent-manager.ts line 193
sed -n '180,210p' apps/desktop/src/main/agent/agent-manager.tsRepository: AndyMik90/Aperant
Length of output: 1375
🏁 Script executed:
# Check resolveAuthFromQueue signature and how it handles abortSignal
sed -n '364,420p' apps/desktop/src/main/ai/auth/resolver.tsRepository: AndyMik90/Aperant
Length of output: 2430
🏁 Script executed:
# List all callers of resolveAuth to identify which ones pass abortSignal
rg -nP 'resolveAuth\s*\(' apps/desktop/src/main -A 2 | grep -E '(resolveAuth|abortSignal)' | head -40Repository: AndyMik90/Aperant
Length of output: 3200
🏁 Script executed:
# Check hasCredentials signature to see if it threads abortSignal
sed -n '320,335p' apps/desktop/src/main/ai/auth/resolver.tsRepository: AndyMik90/Aperant
Length of output: 579
🏁 Script executed:
# Find where getResolvedAuth is called in agent-manager
rg -n 'getResolvedAuth\|resolveAuth' apps/desktop/src/main/agent/agent-manager.ts -B 2 -A 2Repository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Find where resolveAuthFromQueue calls resolveAuth internally
sed -n '364,500p' apps/desktop/src/main/ai/auth/resolver.ts | grep -A 20 -B 5 'resolveAuth'Repository: AndyMik90/Aperant
Length of output: 829
🏁 Script executed:
# Check factory.ts full context around the resolveAuth calls at lines 135 and 276
sed -n '125,145p' apps/desktop/src/main/ai/client/factory.ts
sed -n '265,285p' apps/desktop/src/main/ai/client/factory.tsRepository: AndyMik90/Aperant
Length of output: 1439
🏁 Script executed:
# Search for where getResolvedAuth is called in agent-manager
rg -n 'getResolvedAuth' apps/desktop/src/main/agent/agent-manager.ts -B 5 -A 5Repository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Check the full resolveAuthFromQueue function to find where resolveAuth is called
sed -n '364,650p' apps/desktop/src/main/ai/auth/resolver.ts | head -100Repository: AndyMik90/Aperant
Length of output: 4084
🏁 Script executed:
# Check agent-manager imports and class definition to find getResolvedAuth signature
head -50 apps/desktop/src/main/agent/agent-manager.tsRepository: AndyMik90/Aperant
Length of output: 2404
🏁 Script executed:
# Find the method containing line 193 in agent-manager
sed -n '150,200p' apps/desktop/src/main/agent/agent-manager.tsRepository: AndyMik90/Aperant
Length of output: 2200
🏁 Script executed:
# Search for calls to that method to see if cancellation is needed
rg -n 'getResolvedAuth\|resolveAuthFromQueue' apps/desktop/src/main/agent/agent-manager.ts -B 10 -A 3Repository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Let's check line 150-200 more carefully to understand the context
wc -l apps/desktop/src/main/agent/agent-manager.ts
sed -n '140,200p' apps/desktop/src/main/agent/agent-manager.tsRepository: AndyMik90/Aperant
Length of output: 2667
🏁 Script executed:
# Find the method signature that contains line 193
sed -n '130,145p' apps/desktop/src/main/agent/agent-manager.tsRepository: AndyMik90/Aperant
Length of output: 598
🏁 Script executed:
# Search for all callers of the getResolvedAuth method
rg -n 'getResolvedAuth' apps/desktop/src/main -A 2 -B 2Repository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Check resolveCredentialsForAccount to confirm it threads abortSignal to resolveAuth
rg -n 'resolveCredentialsForAccount' apps/desktop/src/main/ai/auth/resolver.ts -A 20Repository: AndyMik90/Aperant
Length of output: 1537
🏁 Script executed:
# Find all callers of resolveAuthFromProviderQueue to see if they have abortSignal context
rg -n 'resolveAuthFromProviderQueue' apps/desktop/src/main/agent/agent-manager.ts -B 5 -A 5Repository: AndyMik90/Aperant
Length of output: 2412
🏁 Script executed:
# Search for abortSignal usage in agent-manager to understand cancellation patterns
rg -n 'abortSignal' apps/desktop/src/main/agent/agent-manager.ts -B 2 -A 2Repository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Check the full signature and callers of resolveAuthFromProviderQueue
sed -n '130,210p' apps/desktop/src/main/agent/agent-manager.tsRepository: AndyMik90/Aperant
Length of output: 3349
🏁 Script executed:
# Check the signatures of the methods that call resolveAuthFromProviderQueue
sed -n '320,380p' apps/desktop/src/main/agent/agent-manager.ts
sed -n '420,480p' apps/desktop/src/main/agent/agent-manager.ts
sed -n '560,600p' apps/desktop/src/main/agent/agent-manager.tsRepository: AndyMik90/Aperant
Length of output: 8017
🏁 Script executed:
# Search for abortSignal in the broader context of agent-manager tasks
rg -n 'abortSignal|abort|cancel' apps/desktop/src/main/agent/agent-manager.ts | head -20Repository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Check if TaskExecutionOptions includes abortSignal
rg -n 'TaskExecutionOptions\|AbortSignal' apps/desktop/src/main/agent/types.ts -A 10 | head -40Repository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Verify the full method signatures for the three calling methods
sed -n '310,330p' apps/desktop/src/main/agent/agent-manager.ts
sed -n '441,462p' apps/desktop/src/main/agent/agent-manager.ts
sed -n '550,570p' apps/desktop/src/main/agent/agent-manager.tsRepository: AndyMik90/Aperant
Length of output: 2791
🏁 Script executed:
# Check if there's an abort/cancel controller at a higher level in agent-manager
rg -n 'AbortController|cancelTask|abortTask|stop' apps/desktop/src/main/agent/agent-manager.tsRepository: AndyMik90/Aperant
Length of output: 549
🏁 Script executed:
# Verify factory.ts resolveAuth calls include abortSignal
sed -n '130,145p' apps/desktop/src/main/ai/client/factory.ts
sed -n '270,285p' apps/desktop/src/main/ai/client/factory.tsRepository: AndyMik90/Aperant
Length of output: 1134
Thread abortSignal through resolveAuthFromProviderQueue() to enable cancellation on both queue and fallback auth paths.
resolveAuthFromProviderQueue() in agent-manager.ts (line 135) lacks an abortSignal parameter, so cancellation never reaches the fallback resolveAuth() call at line 193. Meanwhile, factory.ts properly threads abortSignal through resolveAuth() calls. Since startSpecCreation, startTaskExecution, and startQAProcess can be cancelled (via stopIdeation/stopRoadmap), auth resolution should respect cancellation.
Add abortSignal?: AbortSignal to resolveAuthFromProviderQueue() signature and pass it to both resolveAuthFromQueue() (line 176) and resolveAuth() (line 193) to match the pattern already established in factory.ts.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/ai/auth/types.ts` around lines 62 - 63, Add an optional
AbortSignal parameter to resolveAuthFromProviderQueue by changing its signature
to accept abortSignal?: AbortSignal, then forward that abortSignal into both
resolveAuthFromQueue(...) and the fallback resolveAuth(...) calls inside
resolveAuthFromProviderQueue so cancellation propagates to the queue path and
the fallback path; mirror the pattern used in factory.ts where resolveAuth calls
receive the abortSignal. Ensure parameter name matches abortSignal and thread it
through any intermediate calls within resolveAuthFromProviderQueue.
| function throwIfAborted(signal?: AbortSignal): void { | ||
| if (!signal?.aborted) return; | ||
|
|
||
| const reason = signal.reason; | ||
| if (reason instanceof Error) { | ||
| throw reason; | ||
| } | ||
|
|
||
| throw new Error(typeof reason === 'string' ? reason : 'Aborted'); | ||
| } |
There was a problem hiding this comment.
Cancellation still turns into phase retries after startup.
This new wrapper only aborts createSimpleClient(). Once the client exists, both phase loops still catch abort errors and append them to errors, so canceling during discovery/features comes back as a normal phase failure after retrying instead of stopping immediately.
🛠️ Suggested guard for both phase loops
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
+ throwIfAborted(abortSignal);
...
} catch (error) {
+ throwIfAborted(abortSignal);
errors.push(`Attempt ${attempt + 1}: ${error instanceof Error ? error.message : String(error)}`);
}
}Apply the same pattern in both runDiscoveryPhase() and runFeaturesPhase().
Also applies to: 500-533
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/ai/runners/roadmap.ts` around lines 41 - 50, The phase
loops still catch aborts and treat them as normal phase failures; update
runDiscoveryPhase() and runFeaturesPhase() to use the same abort-guard pattern
as throwIfAborted/createSimpleClient: when catching errors inside their loops,
immediately rethrow if the provided AbortSignal is aborted (or the caught error
is an AbortError) instead of pushing it into errors — i.e., call
throwIfAborted(signal) at the start of each catch block (or check
signal?.aborted/instanceof Error reason and rethrow) so cancellation stops
execution immediately rather than being recorded as a retriable phase error.
| function readAndRepairJsonFile<T>(filePath: string): T | null { | ||
| try { | ||
| const raw = readFileSync(filePath, 'utf-8'); | ||
| const repaired = repairJson(raw); | ||
|
|
||
| if (repaired !== raw) { | ||
| const tmpFile = `${filePath}.tmp.${process.pid}`; | ||
| writeFileSync(tmpFile, repaired, 'utf-8'); | ||
| renameSync(tmpFile, filePath); | ||
| } | ||
|
|
||
| return JSON.parse(repaired) as T; | ||
| } catch { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Don’t collapse repair write failures into “invalid JSON”.
readAndRepairJsonFile() currently returns null for syntax problems and for readFileSync/writeFileSync/renameSync failures. That same null is treated as “no preserved features” by loadPreservedFeatures(), so an I/O error while persisting a repaired roadmap can silently drop existing planned/in-progress/done items on the next merge. Only map parse failures to null; let filesystem failures surface.
🛠️ Proposed fix
function readAndRepairJsonFile<T>(filePath: string): T | null {
- try {
- const raw = readFileSync(filePath, 'utf-8');
- const repaired = repairJson(raw);
-
- if (repaired !== raw) {
- const tmpFile = `${filePath}.tmp.${process.pid}`;
- writeFileSync(tmpFile, repaired, 'utf-8');
- renameSync(tmpFile, filePath);
- }
-
- return JSON.parse(repaired) as T;
- } catch {
- return null;
+ const raw = readFileSync(filePath, 'utf-8');
+ const repaired = repairJson(raw);
+
+ try {
+ const parsed = JSON.parse(repaired) as T;
+
+ if (repaired !== raw) {
+ const tmpFile = `${filePath}.tmp.${process.pid}`;
+ writeFileSync(tmpFile, repaired, 'utf-8');
+ renameSync(tmpFile, filePath);
+ }
+
+ return parsed;
+ } catch (error) {
+ if (error instanceof SyntaxError) {
+ return null;
+ }
+ throw error;
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function readAndRepairJsonFile<T>(filePath: string): T | null { | |
| try { | |
| const raw = readFileSync(filePath, 'utf-8'); | |
| const repaired = repairJson(raw); | |
| if (repaired !== raw) { | |
| const tmpFile = `${filePath}.tmp.${process.pid}`; | |
| writeFileSync(tmpFile, repaired, 'utf-8'); | |
| renameSync(tmpFile, filePath); | |
| } | |
| return JSON.parse(repaired) as T; | |
| } catch { | |
| return null; | |
| } | |
| function readAndRepairJsonFile<T>(filePath: string): T | null { | |
| const raw = readFileSync(filePath, 'utf-8'); | |
| const repaired = repairJson(raw); | |
| try { | |
| const parsed = JSON.parse(repaired) as T; | |
| if (repaired !== raw) { | |
| const tmpFile = `${filePath}.tmp.${process.pid}`; | |
| writeFileSync(tmpFile, repaired, 'utf-8'); | |
| renameSync(tmpFile, filePath); | |
| } | |
| return parsed; | |
| } catch (error) { | |
| if (error instanceof SyntaxError) { | |
| return null; | |
| } | |
| throw error; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/ai/runners/roadmap.ts` around lines 109 - 123, The
function readAndRepairJsonFile currently swallows all errors (I/O and parse) and
returns null, which masks filesystem failures; change it so only JSON
parse/repair errors map to null while read/write/rename failures are allowed to
throw. Concretely: perform readFileSync/repair/writeFileSync/renameSync and
JSON.parse outside a broad catch or rework the try/catch so that only JSON.parse
(or a caught SyntaxError from parsing the repaired string) is converted to a
null return; do not catch or suppress errors from readFileSync, writeFileSync,
or renameSync. Reference: readAndRepairJsonFile and callers such as
loadPreservedFeatures to ensure they still treat null as "invalid JSON" only.
|
|
||
| try { | ||
| return await refreshOAuthToken(stored.refresh_token, providerSpec, tokenFilePath); | ||
| return await refreshOAuthToken(stored.refresh_token, providerSpec, tokenFilePath, abortSignal); | ||
| } catch (err) { | ||
| if (abortSignal?.aborted) { | ||
| throw (abortSignal.reason instanceof Error ? abortSignal.reason : new Error('Aborted')); | ||
| } |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
Summary
discovery/featuresqueue phases to the UI-facingdiscovering/generatingphases and add focused coverage for that bridgeæ,ø, andåWhy
Roadmap generation had two concrete reliability failures:
Testing
npm run test -- src/main/utils/__tests__/json-repair.test.ts src/main/ai/runners/roadmap.test.ts src/main/__tests__/agent-queue.test.ts src/main/claude-profile/token-refresh.test.tsnpm run typecheckSummary by CodeRabbit
Release Notes
New Features
Bug Fixes
Tests